JWT authentication test examples
- Creating JWT tokens
- Secured endpoints
- Async tests
- Token validation tests
- Expired token tests
- Invalid token tests
This example uses:
PyJWThttpx.AsyncClientpytest+pytest-asyncio
Example Project Structure
app/
├── auth.py
├── main.py
tests/
└── test_jwt_auth.py
1. JWT Utility File (auth.py)
from datetime import datetime, timedelta
from typing import Optional
import jwt
SECRET_KEY = "TEST_SECRET_KEY"
ALGORITHM = "HS256"
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
to_encode = data.copy()
expire = datetime.utcnow() + (expires_delta or timedelta(minutes=15))
to_encode.update({"exp": expire})
return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
2. FastAPI App With JWT Auth (main.py)
from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
import jwt
from app.auth import SECRET_KEY, ALGORITHM
app = FastAPI()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
def verify_token(token: str = Depends(oauth2_scheme)):
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
return payload # contains user_id or email
except jwt.ExpiredSignatureError:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Token expired")
except jwt.InvalidTokenError:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token")
@app.get("/secure")
async def secure_endpoint(payload: dict = Depends(verify_token)):
return {"message": "Access granted", "payload": payload}
3. Async JWT Tests (test_jwt_auth.py)
Install requirements:
pip install pytest pytest-asyncio httpx PyJWT
Test valid JWT
import pytest
from datetime import timedelta
from httpx import AsyncClient
from app.main import app
from app.auth import create_access_token
@pytest.mark.asyncio
async def test_valid_jwt():
token = create_access_token({"sub": "alice"}, expires_delta=timedelta(minutes=5))
async with AsyncClient(app=app, base_url="http://test") as client:
response = await client.get("/secure", headers={"Authorization": f"Bearer {token}"})
assert response.status_code == 200
assert response.json()["message"] == "Access granted"
assert response.json()["payload"]["sub"] == "alice"
Test expired JWT
@pytest.mark.asyncio
async def test_expired_jwt():
# Token expired 1 minute ago
token = create_access_token(
{"sub": "user"},
expires_delta=timedelta(minutes=-1)
)
async with AsyncClient(app=app, base_url="http://test") as client:
response = await client.get("/secure", headers={"Authorization": f"Bearer {token}"})
assert response.status_code == 401
assert response.json() == {"detail": "Token expired"}
Test missing token
@pytest.mark.asyncio
async def test_missing_token():
async with AsyncClient(app=app, base_url="http://test") as client:
response = await client.get("/secure")
assert response.status_code == 401
assert response.json()["detail"] == "Not authenticated"
Test malformed/invalid token
@pytest.mark.asyncio
async def test_invalid_token():
invalid_token = "abc.def.ghi"
async with AsyncClient(app=app, base_url="http://test") as client:
response = await client.get(
"/secure",
headers={"Authorization": f"Bearer {invalid_token}"}
)
assert response.status_code == 401
assert response.json() == {"detail": "Invalid token"}
Test token with wrong secret key
import jwt
@pytest.mark.asyncio
async def test_wrong_secret_jwt():
# Token signed with a different key
fake_token = jwt.encode({"sub": "hacker"}, "WRONG_KEY", algorithm="HS256")
async with AsyncClient(app=app, base_url="http://test") as client:
response = await client.get("/secure", headers={"Authorization": f"Bearer {fake_token}"})
assert response.status_code == 401
assert response.json() == {"detail": "Invalid token"}
Summary of What You Get
These tests validate:
| Test | Validates |
|---|---|
test_valid_jwt | Proper access to protected routes |
test_expired_jwt | Token expiration handling |
test_missing_token | OAuth2 missing header logic |
test_invalid_token | Token structure errors |
test_wrong_secret_jwt | Signature mismatch handling |